#include "Rp2Camera.h"
#include "Rp2Renderer.h"

using namespace Rp2;

//---------------------------------------------------------------------------------------------------
Camera::Camera()
{
	m_pkRenderer = 0;
	Perspective = true;
	//SetFrustum();
}
//---------------------------------------------------------------------------------------------------
Camera::~Camera()
{
}
//---------------------------------------------------------------------------------------------------
void Camera::SetFrame(const Vector3f& rkLocation, const Vector3f& rkRVector,
	const Vector3f& rkUVector, const Vector3f& rkDVector)
{
	m_kLocation = rkLocation;
	SetAxes(rkRVector,rkUVector,rkDVector);
}
//---------------------------------------------------------------------------------------------------
void Camera::SetLocation(const Vector3f& rkLocation)
{
	m_kLocation = rkLocation;
	if (m_pkRenderer)
	{
		m_pkRenderer->OnFrameChange();
	}
}
//---------------------------------------------------------------------------------------------------
void Camera::SetAxes(const Vector3f& rkRVector, const Vector3f& rkUVector, const Vector3f& rkDVector)
{
	m_kRVector = rkRVector;
	m_kUVector = rkUVector;
	m_kDVector = rkDVector;

	float fADet = fabs(m_kDVector.Dot(m_kRVector.Cross(m_kUVector)));
	if (fabs(1.0f - fADet) > 0.01f)
	{
		// The input vectors do not appear to form an orthonormal set.  Time
		// to renormalize.
		Vector3f::OrthoNomalize(m_kRVector, m_kUVector, m_kDVector);
	}

	if (m_pkRenderer)
	{
		m_pkRenderer->OnFrameChange();
	}
}
//---------------------------------------------------------------------------------------------------
void Camera::LookAt(const Vector3f& rkLookAt)
{
	// set camera look at point, R vector is always parallel to xz plane
	Vector3f vDiff = rkLookAt - m_kLocation;
	vDiff.Normalize();
	m_kDVector = vDiff;
	m_kRVector = Vector3f::UNIT_Y.Cross(m_kDVector);
	m_kRVector.Normalize();
	m_kUVector = m_kDVector.Cross(m_kRVector);

	if (m_pkRenderer)
	{
		m_pkRenderer->OnFrameChange();
	}
}
//---------------------------------------------------------------------------------------------------
void Camera::LocationLookAt(const Vector3f& rkLocation, const Vector3f& rkLookAt)
{
	m_kLocation = rkLocation;
	LookAt(rkLookAt);
}
//---------------------------------------------------------------------------------------------------
Vector3f Camera::GetLocation() const
{
	return m_kLocation;
}
//---------------------------------------------------------------------------------------------------
Vector3f Camera::GetDVector() const
{
	return m_kDVector;
}
//---------------------------------------------------------------------------------------------------
Vector3f Camera::GetUVector() const
{
	return m_kUVector;
}
//---------------------------------------------------------------------------------------------------
Vector3f Camera::GetRVector() const
{
	return m_kRVector;
}
//---------------------------------------------------------------------------------------------------
void Camera::SetFrustum(float fFovDegrees, float fAspectRatio, float fDMin, float fDMax)
{
	float fHalfAngleRadians = 0.5f*fFovDegrees*3.141592f/180.0f;
	m_afFrustum[VF_UMAX] = fDMin*(float)tan((double)fHalfAngleRadians);
	m_afFrustum[VF_RMAX] = fAspectRatio*m_afFrustum[VF_UMAX];
	m_afFrustum[VF_UMIN] = -m_afFrustum[VF_UMAX];
	m_afFrustum[VF_RMIN] = -m_afFrustum[VF_RMAX];
	m_afFrustum[VF_DMIN] = fDMin;
	m_afFrustum[VF_DMAX] = fDMax;	

	if (m_pkRenderer)
	{
		m_pkRenderer->OnFrustumChange();
	}
}
//---------------------------------------------------------------------------------------------------
void Camera::SetFrustum (float fRMin, float fRMax, float fUMin, float fUMax,
    float fDMin, float fDMax)
{
    m_afFrustum[VF_RMIN] = fRMin;
    m_afFrustum[VF_RMAX] = fRMax;
    m_afFrustum[VF_UMIN] = fUMin;
    m_afFrustum[VF_UMAX] = fUMax;
    m_afFrustum[VF_DMIN] = fDMin;
    m_afFrustum[VF_DMAX] = fDMax;

    if (m_pkRenderer)
    {
        m_pkRenderer->OnFrustumChange();
    }	
}
//---------------------------------------------------------------------------------------------------
void Camera::SetFrustum (const float* afFrustum)
{
    size_t uiSize = VF_QUANTITY*sizeof(float);
    System::Memcpy(m_afFrustum,uiSize,afFrustum,uiSize);

    if (m_pkRenderer)
    {
        m_pkRenderer->OnFrustumChange();
    }	
}
//---------------------------------------------------------------------------------------------------
void Camera::GetFrustum (float& rfRMin,float& rfRMax,float& rfUMin,float& rfUMax,float& rfDMin,float& rfDMax)
{
	rfRMin = m_afFrustum[VF_RMIN];
    rfRMax = m_afFrustum[VF_RMAX];
	rfUMin = m_afFrustum[VF_UMIN];
	rfUMax = m_afFrustum[VF_UMAX];
	rfDMin = m_afFrustum[VF_DMIN];
	rfDMax = m_afFrustum[VF_DMAX];
}
//---------------------------------------------------------------------------------------------------
void Camera::SetViewport (int iLeft, int iRight, int iTop, int iBottom,
    int iWidth, int iHeight)
{
    if (iWidth > 0 && iHeight > 0)
    {
        assert(0 <= iLeft && iLeft <= iWidth);
        assert(0 <= iRight && iRight <= iWidth);
        assert(0 <= iBottom && iBottom <= iHeight);
        assert(0 <= iTop && iTop <= iHeight);

        m_iWidth	 = iWidth;
        m_iHeight	 = iHeight;
        m_iViewPortL = iLeft;
        m_iViewPortR = iRight;
        m_iViewPortB = iBottom;
        m_iViewPortT = iTop;

        m_fViewPortL = (float)iLeft/(float)iWidth;
        m_fViewPortR = (float)iRight/(float)iWidth;
        m_fViewPortB = (float)iBottom/(float)iHeight;
        m_fViewPortT = (float)iTop/(float)iHeight;
    }
    else
    {
        m_iWidth	 = 0;
        m_iHeight	 = 0;
        m_iViewPortL = 0;
        m_iViewPortR = 0;
        m_iViewPortB = 0;
        m_iViewPortT = 0;

        m_fViewPortL = 0.0f;
        m_fViewPortR = 1.0f;
        m_fViewPortB = 0.0f;
        m_fViewPortT = 1.0f;
    }

    if (m_pkRenderer)
    {
        m_pkRenderer->OnViewportChange();
    }
}
//---------------------------------------------------------------------------------------------------
void Camera::GetViewport (int& riLeft, int& riRight, int& riTop,
    int& riBottom, int& riWidth, int& riHeight)
{
    riLeft		= m_iViewPortL;
    riRight		= m_iViewPortR;
    riTop		= m_iViewPortT;
    riBottom	= m_iViewPortB;
    riWidth		= m_iWidth;
    riHeight	= m_iHeight;
}
//---------------------------------------------------------------------------------------------------
Matrix4f Camera::GetViewMatrix()
{
	Vector3f kEye	  = GetLocation();
	Vector3f kRVector = GetRVector();
	Vector3f kUVector = GetUVector();
	Vector3f kDVector = GetDVector();

	// OpenGL r,u,-d
	Matrix4f kViewMatrix;
	kViewMatrix[ 0] = kRVector.X();
	kViewMatrix[ 1] = kUVector.X();
	kViewMatrix[ 2] = kDVector.X();
	kViewMatrix[ 3] = 0.0f;
	kViewMatrix[ 4] = kRVector.Y();
	kViewMatrix[ 5] = kUVector.Y();
	kViewMatrix[ 6] = kDVector.Y();
	kViewMatrix[ 7] = 0.0f;
	kViewMatrix[ 8] = kRVector.Z();
	kViewMatrix[ 9] = kUVector.Z();
	kViewMatrix[10] = kDVector.Z();
	kViewMatrix[11] = 0.0f;
	kViewMatrix[12] = -kEye.Dot(kRVector);
	kViewMatrix[13] = -kEye.Dot(kUVector);
	kViewMatrix[14] = -kEye.Dot(kDVector);
	kViewMatrix[15] = 1.0f;

	return kViewMatrix;
}
//---------------------------------------------------------------------------------------------------
Matrix4f Camera::GetProjMatrix()
{
	float fRMin, fRMax, fUMin, fUMax, fDMin, fDMax;
	GetFrustum(fRMin,fRMax,fUMin,fUMax,fDMin,fDMax);

	float fInvRDiff = 1.0f/(fRMax - fRMin);
	float fInvUDiff = 1.0f/(fUMax - fUMin);
	float fInvDDiff = 1.0f/(fDMax - fDMin);

	Matrix4f kProjMatrix;
	kProjMatrix[ 0] = 2*fDMin*fInvRDiff;
	kProjMatrix[ 1] = 0.0f;
	kProjMatrix[ 2] = 0.0f;
	kProjMatrix[ 3] = 0.0f;

	kProjMatrix[ 4] = 0.0f;
	kProjMatrix[ 5] = 2*fDMin*fInvUDiff;
	kProjMatrix[ 6] = 0.0f;
	kProjMatrix[ 7] = 0.0f;

	kProjMatrix[ 8] = -(fRMax+fRMin)*fInvRDiff;
	kProjMatrix[ 9] = -(fUMax+fUMin)*fInvUDiff;
	kProjMatrix[10] = (fDMax+fDMin)*fInvDDiff;
	kProjMatrix[11] = 1.0f;

	kProjMatrix[12] = 0.0f;
	kProjMatrix[13] = 0.0f;
	kProjMatrix[14] = -2.0f*fDMax*fDMin*fInvDDiff;
	kProjMatrix[15] = 0.0f;

	return kProjMatrix;
}
//---------------------------------------------------------------------------------------------------